/***************************************************************************
 *   Copyright (C) 2008 by Rysiek Krol,,,   *
 *   rysiek@Babylon2   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "fileoperationshandler.h"

#include "picturemanager.h"
#include "renamedialog.h"
#include "picturelistwidgetitem.h"
#include "viewerevent.h"
#include "progressdialogevent.h"
#include "rexifdata.h"
#include "displayoperations.h"
#include "updateiconevent.h"
#include "eventhandler.h"
#include "Events/fileoperationerrorevent.h"

#include <QListWidget>
#include <QProgressDialog>
#include <QDirIterator>


FileOperationsHandler::FileOperationsHandler(PictureManager* parent, 
                                        FileOperationsHandler::OperationType action,
                                        QList<QFileInfo*> *pliki,
                                        QString newFileName)
					: QThread(parent)
{
    this->isCanceled = false;
    this->yesAnswer = false;
    this->isForAll = false;
    this->showProgressDialogue = true;
    this->lista = pliki;
    this->manager = parent;
    this->action = action;
    this->newFileName = newFileName;
    this->start ( QThread::LowPriority );
}

/**
 * Class constructor
 * @param parent - parent widget (PictureManager)
 * @param action - Operation type
 * @param pliki - file list
 * @param listaAkcji - action list
 * @param showProgressDialogue - show progress dialog?
 */
FileOperationsHandler::FileOperationsHandler(PictureManager * parent, FileOperationsHandler::OperationType action, QList< QFileInfo* > *pliki, QList< ImageLoader::Action > listaAkcji,bool showProgressDialogue): QThread(parent)
{
    this->yesAnswer = false;
    this->isForAll = false;
    this->isCanceled = false;
    this->showProgressDialogue = showProgressDialogue;
    this->lista = pliki;
    this->manager = parent;
    this->action = action;
    this->listaAkcji = listaAkcji;
    this->start ( QThread::NormalPriority );
}


/**
 * Class Destructor
 */
FileOperationsHandler::~FileOperationsHandler()
{
    delete this->iterator;
    delete this->lista;
}

/**
 * Main thread function
 */
void FileOperationsHandler::run()
{
    this->iterator = new QListIterator<QFileInfo*>(*this->lista);
    switch (this->action)
    {
    case FileOperationsHandler::Rename:
        this->renameAction();
        break;

    case FileOperationsHandler::Copy:
        this->copyAction();
        break;

    case FileOperationsHandler::Move:
        this->moveAction();
        break;

    case FileOperationsHandler::Delete:
        this->deleteAction();
        break;

    case FileOperationsHandler::MakeDir:
        this->makeDirAction();
        break;
    case FileOperationsHandler::RemoveComments:
        this->removeCommentsAction();
        break;
    case SaveTransformedImage:
        this->transformAndSaveImage();
        break;
    }
}

/**
* Handles rename action
*/
void FileOperationsHandler::renameAction()
{
    int q = 0;
    if (this->lista->count()>0)
    {
        this->iterator->toFront();
        QFileInfo *fileInfo =this->iterator->next();
        QDir dir = fileInfo->absolutePath();
        this->iterator->toFront();
        while (this->iterator->hasNext() && !this->isCanceled)
        {
            QFileInfo *fileInfo = this->iterator->next();
            if (!fileInfo->isDir())
            {
                if (this->lista->count()==1)
                {
                    QString string(this->newFileName+"."+fileInfo->completeSuffix());
                    if(dir.rename(fileInfo->fileName(), string)){
                        if (dir.cd(".comments"))
                        {
                            string = this->newFileName+"."+fileInfo->completeSuffix()+".xml";
                            dir.rename(fileInfo->fileName()+".xml", string);
                            dir.cdUp();
                        }
                    } else {
                        this->handleOperationError(*fileInfo);
                        if(this->yesAnswer){
                            continue;
                        } else {
                            this->iterator->previous();
                        }
                    }
                }
                else
                {
                    QString string(this->newFileName+"_"+QString::number(q+1)+"."+fileInfo->completeSuffix());
                    dir.rename(fileInfo->fileName(), string);
                    if (dir.cd(".comments"))
                    {
                        string = this->newFileName+"_"+QString::number(q+1)+"."+fileInfo->completeSuffix()+".xml";
                        dir.rename(fileInfo->fileName()+".xml", string);
                        dir.cdUp();
                    }
                    q++;
                }
            }
            else
            {
                if (this->lista->count()==1)
                {
                    dir.rename(fileInfo->fileName(), this->newFileName);
                }
            }
        }
        QApplication::postEvent ( manager, new QEvent ( QEvent::Type ( ViewerEvent::ForceReload ) ) );
    }
}

/**
* Handles delete action
*/
void FileOperationsHandler::deleteAction()
{
    if (this->lista->count()>0)
    {
	this->iterator->toBack();
	int count = 1;
        while (this->iterator->hasPrevious()&& !this->isCanceled)
        {
            QFileInfo *fileInfo = this->iterator->previous();
            if (this->showProgressDialogue)
            {
                ProgressDialogEvent *event = new ProgressDialogEvent(this, count++, this->lista->count(), ProgressDialogEvent::DeleteFiles, fileInfo->fileName() );
                if (!this->isCanceled) QApplication::postEvent ( manager, event );
            }
            //Delete if item is file
            if (!fileInfo->isDir())
            {
                QFile file(fileInfo->absoluteFilePath());

                if(file.remove()){
                    QDir dir(fileInfo->absolutePath());
                    if (dir.cd(".comments")) dir.remove(fileInfo->fileName()+ ".xml");
                } else {
                    //if file cannot be removed
                    this->handleOperationError(*fileInfo);
                    if(this->yesAnswer) this->iterator->next();
                }
            }
            else
            {
                if (!this->isCanceled)this->removeDirectory(QDir(fileInfo->absoluteFilePath()));
            }
        }
        if(this->isCanceled){
             ProgressDialogEvent *event = new ProgressDialogEvent(this, this->lista->count(), this->lista->count(), ProgressDialogEvent::DeleteFiles, "");
             QApplication::postEvent ( manager, event );
        }
        QApplication::postEvent ( manager, new QEvent ( QEvent::Type ( ViewerEvent::ForceReload ) ) );
    }
}


/**
* Handles Copy action
*/
void FileOperationsHandler::copyAction()
{
    if (this->lista->count()>0)
    {
        QDir *baseSourceDir = new QDir(this->iterator->next()->absolutePath());
	QDir *baseTargetDir = new QDir(this->newFileName);
	QDir *commentSourceDir = new QDir();
	QDir *commentTargetDir = new QDir();
        this->iterator->toFront();
        int count=1;
        while (this->iterator->hasNext() && !this->isCanceled)
        {
            QFileInfo *fileInfo = this->iterator->next();
            if (this->showProgressDialogue)
            {
                ProgressDialogEvent *event = new ProgressDialogEvent(this, count++, this->lista->count(), ProgressDialogEvent::CopyFiles, fileInfo->fileName() );
                if (!this->isCanceled) QApplication::postEvent ( manager, event );
            }
            if (fileInfo->isDir())
            {
               bool result = baseTargetDir->mkpath(baseSourceDir->relativeFilePath(fileInfo->absoluteFilePath()));
                if(!result) {
                    //handling error dialog operation
                    if(!this->isForAll){
                        this->handleOperationError(*fileInfo, new QFileInfo(baseSourceDir->relativeFilePath(fileInfo->absoluteFilePath())));
                    }
                    if(this->yesAnswer){
                        this->iterator->previous();
                        continue;
                    }
                }
            }
            else
            {
                QString target;
                if (this->manager->getIsAlbumViewer())
                {
                    target = baseTargetDir->absoluteFilePath(fileInfo->fileName());
                }
                else
                {
                    target = baseTargetDir->absoluteFilePath(baseSourceDir->relativeFilePath(fileInfo->absoluteFilePath()));
                }
                if (!this->isCanceled) {
                   if(!this->copyFiles(fileInfo->absoluteFilePath(), target)){
                        //handling error dialog operation
                        if(!this->isForAll){
                            this->handleOperationError(*fileInfo, new QFileInfo(target));
                        }
                        if(this->yesAnswer){
                            this->iterator->previous();
                            continue;
                        }
                    }
                }

                commentSourceDir->cd(fileInfo->absolutePath());
                if (commentSourceDir->cd(".comments"))
                {
                    commentTargetDir->cd(this->newFileName);
                    commentTargetDir->cd(baseSourceDir->relativeFilePath(fileInfo->absolutePath()));
                    commentTargetDir->mkdir(".comments");
                    if (commentTargetDir->cd(".comments") && !this->isCanceled)
                    {
                       this->copyFiles(commentSourceDir->absoluteFilePath(fileInfo->fileName()+".xml"), commentTargetDir->absoluteFilePath(fileInfo->fileName()+".xml"));
                    }
                }
            }
        }
	delete baseSourceDir;
	delete baseTargetDir;
	delete commentSourceDir;
	delete commentTargetDir ;
    }
    else
    {
        QApplication::postEvent ( manager, new ViewerEvent(ViewerEvent::NoFilesSelectedEvent) );
    }
     if(this->isCanceled){
        ProgressDialogEvent *event = new ProgressDialogEvent(this, this->lista->count(), this->lista->count(), ProgressDialogEvent::RemoveComments, "" );
        QApplication::postEvent ( manager, event );
    }
     QApplication::postEvent ( manager, new QEvent ( QEvent::Type ( ViewerEvent::ForceReload ) ) );

}

/**
* Handles move action
*/
void FileOperationsHandler::moveAction()
{
    if (this->lista->count()>0)
    {
        QDir baseSourceDir(this->iterator->next()->absolutePath());
        QDir baseTargetDir(this->newFileName);
        QDir commentSourceDir;
        QDir commentTargetDir;
        this->iterator->toFront();
        int count=1;
        while (this->iterator->hasNext() && !this->isCanceled)
        {
            QFileInfo *fileInfo = this->iterator->next();
            ProgressDialogEvent *event = new ProgressDialogEvent(this, count++, this->lista->count(), ProgressDialogEvent::MoveFiles, fileInfo->fileName() );
            if (!this->isCanceled) QApplication::postEvent ( manager, event );
            if (fileInfo->isDir())
            {
                if (!this->isCanceled) baseTargetDir.mkdir(baseSourceDir.relativeFilePath(fileInfo->absoluteFilePath()));
            }
            else
            {
                QString target;
                if (this->manager->getIsAlbumViewer())
                {
                    target = baseTargetDir.absoluteFilePath(fileInfo->fileName());
                }
                else
                {
                    target = baseTargetDir.absoluteFilePath(baseSourceDir.relativeFilePath(fileInfo->absoluteFilePath()));
                }
                if (this->copyFiles(fileInfo->absoluteFilePath(), target)) {
                    while(!QFile(fileInfo->absoluteFilePath()).remove()){
                     //handling error dialog operation
                     if(!this->isForAll){
                        this->handleOperationError(*fileInfo);
                        if(!this->yesAnswer)break;
                     }
                    }
                } else {
                     //handling error dialog operation
                     if(!this->isForAll){
                        this->handleOperationError(*fileInfo);
                     }
                    if(this->yesAnswer){
                        this->iterator->previous();
                        continue;
                    }
                }
                commentSourceDir.cd(fileInfo->absolutePath());
                if (commentSourceDir.cd(".comments"))
                {
                    commentTargetDir.cd(this->newFileName);
                    commentTargetDir.cd(baseSourceDir.relativeFilePath(fileInfo->absolutePath()));
                    commentTargetDir.mkdir(".comments");
                    if (commentTargetDir.cd(".comments"))
                    {
                        if (this->copyFiles(commentSourceDir.absoluteFilePath(fileInfo->fileName()+".xml"), commentTargetDir.absoluteFilePath(fileInfo->fileName()+".xml"))){
                            QFile(commentSourceDir.absoluteFilePath(fileInfo->fileName()+".xml")).remove();
                        }
                    }
                }
            }
        }
        this->iterator->toBack();
        while (this->iterator->hasPrevious())
        {
            QFileInfo *fileInfo = this->iterator->previous();
            if (fileInfo->isDir())
            {
                QDir(fileInfo->absoluteFilePath()).rmdir(".comments");
                QDir(fileInfo->absolutePath()).rmdir(fileInfo->fileName());
            }
        }
        QApplication::postEvent ( manager, new QEvent ( QEvent::Type ( ViewerEvent::ForceReload ) ) );
    }
}

/**
* Copying files
*/
bool FileOperationsHandler::copyFiles(QString fromFile, QString toFile)
{
    bool result = false;
    if(!QFileInfo(toFile).exists()){
        int outBytesCount = 0;
        int inBytesCount = 0;

        QFile fileOut(fromFile);
        fileOut.open(QIODevice::ReadOnly);
        QDataStream out(&fileOut);
        char *s = new char[fileOut.size()];
        outBytesCount = out.readRawData(s,fileOut.size());

        fileOut.close();
        if (!this->isCanceled)
        {
            QFile fileIn(toFile);
            fileIn.open(QIODevice::WriteOnly);
            QDataStream in(&fileIn);
            inBytesCount = in.writeRawData(s, fileOut.size());
            fileIn.close();
        }
        if (outBytesCount==inBytesCount) result = true;
        delete s;
    }
    return result;
}

/**
* Makes New Directory
*/
void FileOperationsHandler::makeDirAction()
{
    QDir *curDir = this->manager->getCurDir();
    while(!curDir->mkdir(this->newFileName)){
        //handling error dialog operation
        QFileInfo dir(curDir->absoluteFilePath(this->newFileName));
        this->handleOperationError(dir);
        if(!this->yesAnswer){
            break;
        }
    }
    QApplication::postEvent ( manager, new QEvent ( QEvent::Type ( ViewerEvent::ForceReload ) ) );

}

void FileOperationsHandler::removeCommentsAction()
{
    if (this->lista->count()>0)
    {
        this->iterator->toFront();
        QFileInfo *fileInfo = this->iterator->next();
        QDir dir = fileInfo->absolutePath();
        bool isDirComments =  dir.cd(".comments");
        this->iterator->toFront();
        int count=1;
        while (this->iterator->hasNext() && !this->isCanceled)
        {
            QFileInfo *fileInfo = this->iterator->next();
            ProgressDialogEvent *event = new ProgressDialogEvent(this, count++, this->lista->count(), ProgressDialogEvent::RemoveComments, fileInfo->fileName() );
            QApplication::postEvent ( manager, event );
            if (isDirComments)
            {
                if (!dir.remove(fileInfo->fileName()+".xml"))
                {
                    //handling error dialog operation
                    if(!this->isForAll){
                        QFileInfo commentFile(dir.absoluteFilePath(fileInfo->fileName()+".xml"));
                        this->handleOperationError(commentFile);
                    }
                    if(this->yesAnswer) this->iterator->previous();
                }
            }
        }
        if(this->isCanceled){
                ProgressDialogEvent *event = new ProgressDialogEvent(this, this->lista->count(), this->lista->count(), ProgressDialogEvent::RemoveComments, "" );
        	QApplication::postEvent ( manager, event );
        }
      QApplication::postEvent ( manager, new QEvent ( QEvent::Type ( ViewerEvent::ForceReload ) ) );
    }
}

/**
* Removes directory along witch all contents
*/
void FileOperationsHandler::removeDirectory(QDir dir)
{
    dir.setFilter(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::System|QDir::Files);
    if (dir.count()>0)
    {
        QDirIterator iterator(dir);
        while (iterator.hasNext() && !this->isCanceled)
        {
            QFileInfo fileInfo(iterator.next());
            QString nazwa = fileInfo.fileName();
            if (fileInfo.isDir())
            {
                this->removeDirectory(QDir(fileInfo.absoluteFilePath()));
            }
            else
            {
                dir.remove(fileInfo.fileName());
            }
        }
    }
    QString name = dir.absolutePath();
    dir.cdUp();
    dir.rmdir(name);
}

void FileOperationsHandler::setIsCanceled(bool theValue)
{
    mutex.lock();
    isCanceled = theValue;
    mutex.unlock();
}

/**
* Transform images according to the listaAkcji and saves them
*/
void FileOperationsHandler::transformAndSaveImage()
{
    int count = 1;
    ProgressDialogEvent *event;
    if (this->lista->size()>1)
    {
        if (this->showProgressDialogue)
        {
            event = new ProgressDialogEvent(this, 1, this->lista->size(), ProgressDialogEvent::SaveImageFile, "");
            QApplication::postEvent ( manager, event );
        }
    }
    // Transform all files on list
    while (this->iterator->hasNext() && !this->isCanceled)
    {
        QFileInfo *file = this->iterator->next();
        // Update Progress dialog
        if (this->lista->size()==1 )
        {
            if (this->showProgressDialogue)
            {
                event = new ProgressDialogEvent(this, 1, 100, ProgressDialogEvent::SaveImageFile, file->fileName() );
                QApplication::postEvent ( manager, event );
            }
        }
        else
        {
            if (this->showProgressDialogue)
            {
                event = new ProgressDialogEvent(this, count++, this->lista->size(), ProgressDialogEvent::SaveImageFile, file->fileName() );
                QApplication::postEvent ( manager, event );
            }
        }
        // Transform image
        QImage *image = new QImage(file->absoluteFilePath());
        QListIterator<ImageLoader::Action> actionIterator(this->listaAkcji);
        if (this->lista->size()==1 && this->showProgressDialogue)
        {
            event = new ProgressDialogEvent(this, 20, 100, ProgressDialogEvent::SaveImageFile, file->fileName() );
            QApplication::postEvent ( manager, event );
        }
        while (actionIterator.hasNext())
        {
            switch (actionIterator.next())
            {
            case ImageLoader::Turn90 :
                this->operationRoate(image, 90);
                break;
            case ImageLoader::Turn180 :
                this->operationRoate(image, 180);
                break;
            case ImageLoader::Turn270 :
                this->operationRoate(image, 270);
                break;
            default:
                break;
            }
        }
        if (this->lista->size()==1 && this->showProgressDialogue)
        {
            event = new ProgressDialogEvent(this, 50 , 100, ProgressDialogEvent::SaveImageFile, file->fileName() );
            QApplication::postEvent ( manager, event );
        }
        // Make new Exif thumbnail
        RExifData *exifData = new RExifData(file->absoluteFilePath().toUtf8().data());
        if(!image->save(file->absoluteFilePath())){
            if (this->lista->size()==1 && this->showProgressDialogue)
            {
                event = new ProgressDialogEvent(this, 90 , 100, ProgressDialogEvent::SaveImageFile, file->fileName() );
                QApplication::postEvent ( manager, event );
            }
            exifData->writeData();

            delete image;
            if (this->lista->size()==1 && this->showProgressDialogue)
            {
                event = new ProgressDialogEvent(this, 100 , 100, ProgressDialogEvent::SaveImageFile, file->fileName() );
                QApplication::postEvent ( manager, event );
            }
        } else {
            this->handleOperationError(*file);
            if(this->yesAnswer) {
                continue;
            } else {
                if(this->iterator->hasPrevious())this->iterator->previous();
            }
        }
        delete exifData;
        QApplication::postEvent(this->manager->getEventHandler(), new UpdateIconEvent(new QFileInfo(*file)) );
    }
}

/**
* Roate QImage image by degree degree
*/
void FileOperationsHandler::operationRoate(QImage * image, int degree)
{
    QMatrix matrix;
    matrix.rotate(degree);
    *image = image->transformed(matrix, Qt::SmoothTransformation);
}

/**
* Set value if answer applys to al files
*/
void FileOperationsHandler::setYesAnswer(bool value){
    this->yesAnswer = value;
}

/**
* Sets value if answer to error question is Yes;
*/
void FileOperationsHandler::setForAll(bool value){
    this->isForAll = value;
}

/**
* Finds out what was the cause of file operation error
*/
FileOperationsHandler::ErrorCause FileOperationsHandler::findErrorCause(QFileInfo &fileInfo, QFileInfo *toFileinfo){
	FileOperationsHandler::ErrorCause cause = FileOperationsHandler::Unknown;
        switch(this->action){
            case FileOperationsHandler::MakeDir: {
                    if(fileInfo.exists()) return FileOperationsHandler::FileExist;
                    if(!QFileInfo(fileInfo.absolutePath()).isWritable()) return FileOperationsHandler::PermissionDenined;
                    break;
                }
            case FileOperationsHandler::RemoveComments:{
                    if(!fileInfo.exists()) return FileOperationsHandler::FileDoesNotExist;
                    if(!fileInfo.isWritable()) return FileOperationsHandler::PermissionDenined;
                    break;
                }
            case FileOperationsHandler::Copy:{
                        if(!fileInfo.exists()) return FileOperationsHandler::FileDoesNotExist;
                        if(!fileInfo.isReadable()) return FileOperationsHandler::CannotReadFile;
                        if(toFileinfo->exists()) return FileOperationsHandler::FileExist;
                        if(!QFileInfo(toFileinfo->absolutePath()).isWritable()) return FileOperationsHandler::PermissionDenined;
                        break;
            }
            case FileOperationsHandler::Move:{
                        if(!fileInfo.exists()) return FileOperationsHandler::FileDoesNotExist;
                        if(!fileInfo.isReadable()) return FileOperationsHandler::CannotReadFile;
                        if(!fileInfo.isWritable()) return FileOperationsHandler::CannotDeleteFile;
                        if(toFileinfo->exists()) return FileOperationsHandler::FileExist;
                        if(!QFileInfo(toFileinfo->absolutePath()).isWritable()) return FileOperationsHandler::PermissionDenined;
                        break;
            }
            case FileOperationsHandler::SaveTransformedImage:{
                        if(!fileInfo.exists()) return FileOperationsHandler::FileDoesNotExist;
                        if(!fileInfo.isWritable()) return FileOperationsHandler::PermissionDenined;
                        break;
            }
                default:
                break;
        }
	return cause;
}

/**
* Handles operation error
*/
void FileOperationsHandler::handleOperationError(QFileInfo &fileInfo, QFileInfo *toFileInfo){
    if(!this->isForAll){
        bool manyFiles = false;
        if (this->lista->count()>1) manyFiles = true;
        FileOperationErrorEvent *event = new FileOperationErrorEvent(this, this->action, fileInfo.fileName());
        event->setCause(this->findErrorCause(fileInfo, toFileInfo));
        event->setManyFiles(manyFiles);
        QApplication::postEvent ( this->manager->getEventHandler(), event );
        this->waitMutex.lock();
        this->waitError.wait(&this->waitMutex);
        this->waitMutex.unlock();
    }
    delete toFileInfo;
}
